package com.jdpc.weparty.service.user

import com.alibaba.fastjson.JSONObject
import com.jdpc.weparty.component.ServerConfig
import com.jdpc.weparty.entity.Activity
import com.jdpc.weparty.entity.Role
import com.jdpc.weparty.entity.User
import com.jdpc.weparty.repository.RoleRepository
import com.jdpc.weparty.repository.UdrRepository
import com.jdpc.weparty.reposity.UserRepository
import com.jdpc.weparty.security.JwtTokenUtil
import com.jdpc.weparty.security.JwtUser
import com.jdpc.weparty.service.udr.UdrService
import okhttp3.OkHttpClient
import okhttp3.Request
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.authentication.DisabledException
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.stereotype.Service
import java.util.*

@Service
class UserServiceImpl : UserService {
    @Autowired
    private lateinit var serverConfig: ServerConfig

    @Autowired
    private lateinit var authenticationManager: AuthenticationManager

    @Autowired
    private lateinit var userDetailsService: UserDetailsService

    @Autowired
    private lateinit var jwtTokenUtil: JwtTokenUtil

    @Autowired
    private lateinit var userRepository: UserRepository

    @Autowired
    private lateinit var udrService: UdrService

    @Autowired
    private lateinit var udrRepository: UdrRepository

    @Autowired
    private lateinit var roleRepository: RoleRepository

//    @Value("\${jwt.tokenHead}")
//    private lateinit var tokenHead: String

    @Value("\${wxapp.appid}")
    private lateinit var appId: String

    @Value("\${wxapp.appsecret}")
    private lateinit var appsecret: String

    override fun login(account: String, pwd: String): Pair<String?, Int?> {
        val user = userRepository.findByPhoneNumber(account)
        user?.let {
            if(BCryptPasswordEncoder().matches(pwd,user.password)){
                return generateToken(account, pwd)
            }
        }
        return Pair<String?, Int?>(null,null)
    }

    override fun login(wxcode: String): Pair<String?, Int?> {
        val pair = code2session(wxcode)
        val wxOpenId = pair.first
        val sessionKey = pair.second
        if (wxOpenId == null || sessionKey == null) {
            return Pair(null, null)
        }
        val user = userRepository.findByWxOpenId(wxOpenId)

        return user?.let {
            val userDetails = userDetailsService.loadUserByUsername(it.phoneNumber) as JwtUser
            generateToken(userDetails, sessionKey)
        } ?: Pair(null, null)
    }

    /**
     * 生成微信第三方登录token
     */
    private fun generateToken(jwtUser: JwtUser, sessionKey: String): Pair<String, Int> {

        return Pair(jwtTokenUtil.generateToken(jwtUser, sessionKey), jwtUser.id)
    }

    /**
     * 生成该系统账号密码token
     */
    private fun generateToken(account: String, pwd: String): Pair<String, Int> {
        val upToken = UsernamePasswordAuthenticationToken(account, pwd)
        try {
            val authentication = authenticationManager.authenticate(upToken)
            SecurityContextHolder.getContext().authentication = authentication
        } catch (e: DisabledException) {
            println("DISABLE")
            e.printStackTrace()
            throw DisabledException(e.message)
        } catch (e: BadCredentialsException) {
            println("INVALID_CREDENTIALS")
            e.printStackTrace()
            throw BadCredentialsException(e.message)
        }

        val userDetails = userDetailsService.loadUserByUsername(account) as JwtUser
        return Pair(jwtTokenUtil.generateToken(userDetails), userDetails.id)
    }

    /**
     * @return Pari(openId,sessionKey)
     */
    private fun code2session(wxcode: String): Pair<String?, String?> {
        val grant_type = "authorization_code"
        val url = "https://api.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${appsecret}" +
                "&js_code=${wxcode}&grant_type=${grant_type}"
        val okHttpClient = OkHttpClient()
        val request = Request.Builder()
                .url(url)
                .get()
                .build()
        val call = okHttpClient.newCall(request)
        val response = call.execute()
        val jsonObject = JSONObject.parseObject(response.body().string())
        val sessionKey = jsonObject.getString("session_key")
        val openId = jsonObject.getString("openid")
        return Pair(openId, sessionKey)
    }

    override fun refresh(oldToken: String): String? {
        val token: String = oldToken.substring(serverConfig.tokenHead.length)
        val username = jwtTokenUtil.getAccountFromToken(token)
        val user = userDetailsService.loadUserByUsername(username) as JwtUser
        return if (jwtTokenUtil.canTokenBeRefreshed(token, user.lastPasswordResetDate)) {
            jwtTokenUtil.refreshToken(token)!!
        } else null
    }

    override fun resetPassword(account: String, pwd: String) {
        val user = userRepository.findByPhoneNumber(account)
        if (user != null) {
            val encoder = BCryptPasswordEncoder()
            user.password = encoder.encode(pwd)
            user.lastPasswordResetDate = Date()
            userRepository.save(user)
        }
    }

    override fun getExaminer(activity: Activity): User {
        return userRepository.findByExaminedActivities(activity)

    }

    override fun getPublisher(activity: Activity): User = userRepository.findByPublishedActivities(activity)

    override fun getUser(id: Int): User? {
        val option = userRepository.findById(id)
        return if (option.isPresent) {
            option.get()
        } else {
            null
        }
    }

    override fun getUser(account: String): User? {
        return userRepository.findByPhoneNumber(account)
    }

    override fun changeInfo(id: Int, info: UserInfo): Boolean {
        val option = userRepository.findById(id)
        return if (option.isPresent) {
            val user = option.get()
            if (info.phoneNumber.trim() == "") {
                //默认不会修改手机号
                info.phoneNumber = user.phoneNumber
            }
            if (info.avatar.trim() == "") {
                info.avatar = user.avatar
            }
            user.setUserInfo(info)
            userRepository.save(user)
            true
        } else {
            false
        }
    }

    override fun register(info: UserInfo, password: String): User? {
        if (info.phoneNumber.trim() != "") {
            val u = userRepository.findByPhoneNumber(info.phoneNumber)
            u?.let {
                return null
            }
        }
        val user = User().apply {
            setUserInfo(info)
        }
        val encoder = BCryptPasswordEncoder()
        user.password = encoder.encode(password)
        user.lastPasswordResetDate = Date()
        val result = userRepository.save(user)
        udrService.save(result, Role.RoleLevel.DEFAULT_LEVEL)
        return result
    }

    override fun pickOneExaminer(): User {
        val role = roleRepository.findByName(Role.RoleType.ROLE_ADMIN)
        val admins = udrRepository.findByRole(role)?.map {
            it.user
        }
        var examinedNum = Int.MAX_VALUE
        var result = User()
        admins?.forEach {
            it?.let {
                val size = it.examinedActivities.size
                if (examinedNum > size) {
                    result = it
                    examinedNum = size
                }
            }
        }
        return result
    }

    override fun bindWXAccount(user: User, code: String): Pair<String, Int> {
        val openId = code2session(code).first
        openId?.let {
            user.wxOpenId = openId
            userRepository.save(user)
            return Pair("绑定成功", 0)
        }
        return Pair("绑定微信失败，无法获取openId，请稍后再试", -1)
    }

}